
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>5 基本操作 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch06.html" />
    
    
    <link rel="prev" href="ch04.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >5 基本操作</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="65"></a></p>
<div class="chapter-number"><p class="chapter-number">5</p></div>

<h1 id="essential-operations">基本操作 </h1>
<blockquote>
<p>如果有人说</p>
<p>“我想要一种编程语言，</p>
<p>不论什么功能，只要许个愿就行”，</p>
<p>给他个棒棒糖吧。</p>
<p><span title="出自文章《Epigrams on Programming》：https://en.wikipedia.org/wiki/Epigrams_on_Programming">—— Alan Perlis</span><sup><a href="#fn_1" id="reffn_1">1</a></sup></p>
</blockquote>
<h2 id="5.1">5.1 导言 </h2>
<p>有些操作，比如初始化、赋值、拷贝和转移，从语言规则所做的假设来看，属于基础操作。
而其它一些，比如<code>==</code>和<code>&lt;&lt;</code>，其意义约定俗成，不遵从这种约定，麻烦就大了。</p>
<h3 id="5.1.1">5.1.1 基本操作 </h3>
<p>在众多设计中，对象的构建至关重要。
其用法的多样性体现在语言特性对初始化操作支持的广度和灵活性上。</p>
<p>类型的构造函数、析构函数以及拷贝、转移这些操作的逻辑关系盘根错节。
定义它们的时候一定要彼此配合，否则就会带来逻辑或者性能方面的问题。</p>
<p><a class="en-page-number" id="66"></a></p>
<p>如果一个类<code>X</code>有析构函数，它执行一些不容忽视的任务，
比如自由存储区资源回收或者释放锁，那这个类很可能就需要整一套全活了：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">X</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">X</span>(Sometype)             <span class="hljs-comment">// “常规构造函数”：创建对象</span>
    <span class="hljs-built_in">X</span>();                    <span class="hljs-comment">// 缺省构造函数</span>
    <span class="hljs-built_in">X</span>(<span class="hljs-type">const</span> X&amp;)             <span class="hljs-comment">// 拷贝构造函数</span>
    <span class="hljs-built_in">X</span>(X&amp;&amp;);                 <span class="hljs-comment">// 转移构造函数</span>
    X&amp; <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> X&amp;); <span class="hljs-comment">// 拷贝赋值：清理目标对象并拷贝</span>
    X&amp; <span class="hljs-keyword">operator</span>=(X&amp;&amp;);      <span class="hljs-comment">// 转移赋值：清理目标对象并转移</span>
    ～<span class="hljs-built_in">X</span>();                  <span class="hljs-comment">// 析构函数：清理资源</span>
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>对象的拷贝和转移有五种情形：</p>
<ul>
<li>赋值给另一个对象</li>
<li>为一个对象提供初值</li>
<li>作为函数参数</li>
<li>作为函数的返回值</li>
<li>作为一个异常</li>
</ul>
<p>赋值操作采用拷贝或者转移运算符。原则上，其它情形均使用拷贝或者构造函数。
不过，拷贝或转移构造函数的调用通常会被优化掉，方法是把初始值直接在该对象上进行构造。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function">X <span class="hljs-title">make</span><span class="hljs-params">(Sometype)</span></span>;
X x = <span class="hljs-built_in">make</span>(value);
</code></pre>
<p>此处，编译器通常把<code>make()</code>返回的<code>X</code>在<code>x</code>上构造；从而消除（“省略”）掉一次拷贝。</p>
<p>除了初始化具名对象和分配在自由存储区上的对象之外，
构造函数也被用于构造临时对象，还被用于实现显式类型转换。</p>
<p>除了“常规构造函数”，以下这些成员函数也会在按需被编译器生成。
如果想显式操控默认实现的生成，可以这样：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Y</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Y</span>(Sometype);
    <span class="hljs-built_in">Y</span>(<span class="hljs-type">const</span> Y&amp;) = <span class="hljs-keyword">default</span>;  <span class="hljs-comment">// 我确定想要默认的拷贝构造函数</span>
    <span class="hljs-built_in">Y</span>(Y&amp;&amp;) = <span class="hljs-keyword">default</span>;       <span class="hljs-comment">// 以及默认的转移构造函数</span>
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>如果你显式生成了一部分默认实现，那其它的缺省定义就不会再生成了。</p>
<p>如果某个类具有指针成员变量，那么显式定义拷贝和转移操作通常是比较明智的。
原因是指针可能指向某个资源，需要类去<code>delete</code>，这种情况下，
默认将成员采用的逐个复制操作会导致错误。
又或者，这个指针指向的资源，要求类绝对<em>不能</em><code>delete</code>。
无论属于哪种情况，代码的读者都需要弄清楚。相关例子，请参阅 §5.2.1。</p>
<p><a class="en-page-number" id="67"></a></p>
<p>一个值得力荐的规则（有时候也叫<em>零规则（the rule of zero）</em>）是：
要么定义全部基础操作，要么全不定义（全用默认实现）。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Z</span> {
    Vector v;
    string s;
};

Z z1;       <span class="hljs-comment">// 默认初始化 z1.v 和 z1.s</span>
Z z2 = z1;  <span class="hljs-comment">// 默认拷贝 z1.v 和 z1.s</span>
</code></pre>
<p>本例中，在有需求的情况下，
编译器会合成逐成员操作的默认构造函数、拷贝、转移和析构函数，且语意全都正确。</p>
<p>与<code>=default</code>相对，<code>=delete</code>用于表示拒绝生成某个操作。
类层次中的基类是个经典的例子，这种情况下，我们要禁止将成员逐个复制的操作。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Shape</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Shape</span>(<span class="hljs-type">const</span> Shape&amp;) =<span class="hljs-keyword">delete</span>;            <span class="hljs-comment">// 没有拷贝操作</span>
    Shape&amp; <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Shape&amp;) =<span class="hljs-keyword">delete</span>;
    <span class="hljs-comment">// ...</span>
};

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">copy</span><span class="hljs-params">(Shape&amp; s1, <span class="hljs-type">const</span> Shape&amp; s2)</span>
</span>{
    s1 = s2;    <span class="hljs-comment">// 报错：Shape的拷贝操作已移除</span>
}
</code></pre>
<p><code>=delete</code>会导致被<code>delete</code>函数的使用触发编译器报错；
<code>=delete</code>可用于屏蔽任何函数，不仅仅是基本成员函数。</p>
<h3 id="5.1.2">5.1.2 类型转换 </h3>
<p>仅接受单个参数的构造函数定义了源自参数类型的转换。
例如，<code>complex</code>(§4.2.1)有个源自<code>double</code>的构造函数：</p>
<pre><code class="lang-cpp">complex z1 = <span class="hljs-number">3.14</span>;  <span class="hljs-comment">// z1 成为 {3.14,0.0}</span>
complex z2 = z1*<span class="hljs-number">2</span>;  <span class="hljs-comment">// z2 成为 z1*{2.0,0} == {6.28,0.0}</span>
</code></pre>
<p>有时候这种隐式转换很理想，但也不总那么理想。
例如，<code>Vector</code>(§4.2.2)有一个源自<code>int</code>的构造函数：</p>
<pre><code class="lang-cpp">Vector v1 = <span class="hljs-number">7</span>; <span class="hljs-comment">// OK：v1有7个元素</span>
</code></pre>
<p>这通常都不是个隐患，标准库的<code>vector</code>就禁止<code>int</code>到<code>vector</code>的“类型转换”。</p>
<p>避免这一问题的途径是仅允许显式“类型转换”；就是说这样定义构造函数：</p>
<p><a class="en-page-number" id="68"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">Vector</span><span class="hljs-params">(<span class="hljs-type">int</span> s)</span></span>; <span class="hljs-comment">// 不会隐式从 int 转换到 Vector</span>
    <span class="hljs-comment">// ... </span>
};
</code></pre>
<p>效果是这样的：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Vector <span class="hljs-title">v1</span><span class="hljs-params">(<span class="hljs-number">7</span>)</span></span>;   <span class="hljs-comment">// OK： v1 有 7 个元素</span>
Vector v2 = <span class="hljs-number">7</span>;  <span class="hljs-comment">// 报错：不能从int隐式转换到Vector</span>
</code></pre>
<p>涉及类型转换到时候，更多的类型像<code>Vector</code>，而不像<code>complex</code>，
因此，应该把<code>explicit</code>用于单参数的构造函数，除非有好理由不用它。</p>
<h3 id="5.1.3">5.1.3 成员变量初始值 </h3>
<p>当某个类定义了数据成员，我们应该为其提供默认初始值，这被称为<em>成员变量默认初始值</em>。
考虑一下<code>complex</code>(§4.2.1)的这个新版本：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">complex</span> {
    <span class="hljs-type">double</span> re = <span class="hljs-number">0</span>;
    <span class="hljs-type">double</span> im = <span class="hljs-number">0</span>;  <span class="hljs-comment">// 表征数据：两个默认值为 0.0 的 double</span>
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">complex</span>(<span class="hljs-type">double</span> r, <span class="hljs-type">double</span> i) :re{r}, im{i} {}  <span class="hljs-comment">// 用两个标量构造complex：{r,i}</span>
    <span class="hljs-built_in">complex</span>(<span class="hljs-type">double</span> r) :re{r} {}                   <span class="hljs-comment">// 用一个标量构造complex：{r,0}</span>
    <span class="hljs-built_in">complex</span>() {}                                  <span class="hljs-comment">// 默认的complex：{0,0}</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>只要构造函数未给定一个值，就会应用默认值。
这可以简化编码，并有助于避免粗心大意导致的成员变量未初始化。</p>
<h2 id="5.2">5.2 拷贝和转移 </h2>
<p>默认情况下，对象可以被复制。无论对用户定义类型还是内置类型，这都成立。
默认的复制是逐成员的：复制每个成员变量。例如使用 §4.2.1 的<code>complex</code>：</p>
<pre><code class="lang-cpp">
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">test</span><span class="hljs-params">(complex z1)</span> 
</span>{
    complex z2{z1}; <span class="hljs-comment">// 拷贝初始化 </span>
    complex z3;
    z3 = z2;        <span class="hljs-comment">// 拷贝赋值</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>现在<code>z1</code>、<code>z2</code>和<code>z3</code>的值相同，因为不论赋值还是初始化函数，
都把两个成员变量全复制了。</p>
<p>当我们设计类时，必须要始终考虑是否以及如何复制一个对象。
对于简单的实体类型，逐成员复制通常是正确的语意。</p>
<p><a class="en-page-number" id="69"></a></p>
<p>但是对于某些精细的实体类型，比如<code>Vector</code>，
逐成员复制并非正确语意，对于抽象类型则几乎从来不是。</p>
<h3 id="5.2.1">5.2.1 容器复制 </h3>
<p>当某个类是一个<em>资源执柄（resource handle）</em>
——就是说，当该类负责通过指针访问某个对象——默认的逐成员复制通常是个灾难。
逐成员复制会违反资源执柄的不变式(§3.5.2)。
例如，默认复制会导致<code>Vector</code>的一个副本指向与原件相同的元素：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">bad_copy</span><span class="hljs-params">(Vector v1)</span>
</span>{
    Vector v2 = v1; <span class="hljs-comment">// 把v1的表征数据复制到v2</span>
    v1[<span class="hljs-number">0</span>] = <span class="hljs-number">2</span>;      <span class="hljs-comment">// v2[0] 现在也是2！</span>
    v2[<span class="hljs-number">1</span>] = <span class="hljs-number">3</span>;      <span class="hljs-comment">// v1[1] 现在也是3！</span>
}
</code></pre>
<p>假设<code>v1</code>有四个元素，结果可以图示如下：</p>
<p><img src="img/ch05_01.png" alt="bad_copy-result"></p>
<p>万幸的是，<code>Vector</code>具有析构函数的事实强烈暗示了“默认（逐成员）的复制语意不对”，
而编译器起码应该对本例给出警告。
我们需要定义一个更优的复制语意。</p>
<p>某个类对象的复制由两个成员函数定义：
<em>拷贝构造函数（copy constructor）</em>和<em>拷贝赋值函数（copy assignment）</em>：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;   <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-type">int</span> sz;
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s);                          <span class="hljs-comment">// 构造函数：建立不变式，申请资源</span>
    ~<span class="hljs-built_in">Vector</span>() { <span class="hljs-keyword">delete</span>[] elem; }            <span class="hljs-comment">// 析构函数：释放资源</span>

    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">const</span> Vector&amp; a);                <span class="hljs-comment">// 拷贝构造</span>
    Vector&amp; <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Vector&amp; a);     <span class="hljs-comment">// 拷贝赋值</span>

    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);
    <span class="hljs-type">const</span> <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) <span class="hljs-type">const</span>;

    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span></span>;
};
</code></pre>
<p>一个合格的<code>Vector</code>拷贝构造函数的定义，要按元素数量所需分配存储空间，
然后把元素复制到里面，以便在复制后每个<code>Vector</code>都有它自己的元素副本：</p>
<p><a class="en-page-number" id="70"></a></p>
<pre><code class="lang-cpp">Vector::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">const</span> Vector&amp; a) <span class="hljs-comment">// 拷贝构造函数</span>
    :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[a.sz]},    <span class="hljs-comment">// 为元素分配存储空间</span>
    sz{a.sz}
{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=sz; ++i)   <span class="hljs-comment">// 复制元素</span>
        elem[i] = a.elem[i];
}
</code></pre>
<p>现在，<code>v2=v1</code>示例的结果可以这样表示：</p>
<p><img src="img/ch05_02.png" alt="copy-constructor-result"></p>
<p>在成员函数里，名称<code>this</code>是预定义的，它指向调用该成员函数的那个对象。</p>
<h3 id="5.2.2">5.2.2 容器转移 </h3>
<p>我们可以通过定义拷贝构造函数和拷贝赋值函数来控制复制操作，
但对于庞大的容器，复制操作代价高昂。
在传递一个对象给函数时，可以采用引用，从而避免复制的代价，
但却不能返回一个指向局部对象的引用作为结果
（在调用者查看的时候，局部变量就已经被销毁了），考虑这个：</p>
<pre><code class="lang-cpp">Vector <span class="hljs-keyword">operator</span>+(<span class="hljs-type">const</span> Vector&amp; a, <span class="hljs-type">const</span> Vector&amp; b)
{
    <span class="hljs-keyword">if</span> (a.<span class="hljs-built_in">size</span>()!=b.<span class="hljs-built_in">size</span>())
        <span class="hljs-keyword">throw</span> Vector_size_mismatch{};

    <span class="hljs-function">Vector <span class="hljs-title">res</span><span class="hljs-params">(a.size())</span></span>;
</code></pre>
<p><a class="en-page-number" id="71"></a></p>
<pre><code class="lang-cpp">    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=a.<span class="hljs-built_in">size</span>(); ++i)
        res[i]=a[i]+b[i];
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<p>从<code>+</code>返回一个值，涉及到复制局部变量<code>res</code>出去，并且放置于某个调用者可以访问的位置。
该<code>+</code>操作可以这么用：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">const</span> Vector&amp; x, <span class="hljs-type">const</span> Vector&amp; y, <span class="hljs-type">const</span> Vector&amp; z)</span>
</span>{
    Vector r;
    <span class="hljs-comment">// ...</span>
    r = x+y+z;
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>这起码要复制<code>Vector</code>两次（在每次调用<code>+</code>时候）。
如果某个<code>Vector</code>很大，比方说有 10,000 个 double，可就太令人汗颜了。
最汗颜的是<code>operator+()</code>里的<code>res</code>复制后再没用过。
我们不是真的想复制；而是想把结果从函数里弄出来：
想要<em>转移（move）</em>一个<code>Vector</code>，而非<em>复制(copy)</em>它。
万幸的是，我们可以表明这个意图：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
    <span class="hljs-comment">// ...</span>

    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">const</span> Vector&amp; a);            <span class="hljs-comment">// 拷贝构造函数</span>
    Vector&amp; <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Vector&amp; a); <span class="hljs-comment">// 拷贝赋值函数</span>

    <span class="hljs-built_in">Vector</span>(Vector&amp;&amp; a);                 <span class="hljs-comment">// 转移构造函数</span>
    Vector&amp; <span class="hljs-keyword">operator</span>=(Vector&amp;&amp; a);      <span class="hljs-comment">// 转移赋值函数</span>
};
</code></pre>
<p>根据这个定义，编译器在把返回值传出函数时，
将使用<em>转移构造函数（move constructor）</em>执行。
这意味着，<code>r=x+y+z</code>将不涉及<code>Vector</code>的复制。
取而代之的是，<code>Vector</code>仅被转移了。</p>
<p>与之相似，<code>Vector</code>转移构造函数的定义也是小菜一碟：</p>
<pre><code class="lang-cpp">Vector::<span class="hljs-built_in">Vector</span>(Vector&amp;&amp; a)
    :elem{a.elem},      <span class="hljs-comment">// 从a“拿来元素”</span>
    sz{a.sz}
{
    a.elem = <span class="hljs-literal">nullptr</span>;   <span class="hljs-comment">// a现在没有元素了</span>
    a.sz = <span class="hljs-number">0</span>;
}
</code></pre>
<p><code>&amp;&amp;</code>的意思是“右值引用（rvalue reference）”，是个可以绑定到右值的引用。
“右值（rvalue）”这词有意跟“左值（lvalue）”相对，
“左值”大体上是“可以出现在赋值左侧的东西”。
因此右值——大概其——是不能对其赋值的东西，比如函数调用返回的某个整数。
这样，右值引用就是一个<em>没有其他人（nobody else）</em>能为其赋值的东西，
所以可以安全地“偷取”其值。
<code>operator+()</code>中的局部变量<code>res</code>就是一个例子。</p>
<p><a class="en-page-number" id="72"></a></p>
<p>转移构造函数<em>不</em>接受<code>const</code>参数：毕竟，它本该从其参数中把值移除。
<em>转移赋值函数（move assignment）</em>的定义相似。</p>
<p>每当右值引用作为初值，或作为赋值右侧的值，就应用转移操作。</p>
<p>转移操作之后，“移自”对象所处的状态应允许执行析构函数。
一般来说，也允许向一个“移自”对象赋值。
标准库算法（第12章）假定如此。
我们的<code>Vector</code>也是这样。</p>
<p>程序员知道某个值再也不会用到了，但编译器不见得这么聪明，程序员可以指明：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Vector <span class="hljs-title">f</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-function">Vector <span class="hljs-title">x</span><span class="hljs-params">(<span class="hljs-number">1000</span>)</span></span>;
    <span class="hljs-function">Vector <span class="hljs-title">y</span><span class="hljs-params">(<span class="hljs-number">2000</span>)</span></span>;
    <span class="hljs-function">Vector <span class="hljs-title">z</span><span class="hljs-params">(<span class="hljs-number">3000</span>)</span></span>;
    z = x;              <span class="hljs-comment">// 执行复制（x可能在f()后续被用到）</span>
    y = std::<span class="hljs-built_in">move</span>(x);   <span class="hljs-comment">// 执行转移（转移复制函数）</span>
    <span class="hljs-comment">// ... 最好别在这用x了 ...</span>
    <span class="hljs-keyword">return</span> z;           <span class="hljs-comment">// 执行转移</span>
}
</code></pre>
<p>标准库函数<code>move()</code>并不真移动什么。
而是返回其（我们想转移的）参数的引用——<em>右值引用</em>；实际上进行了类型转换(§4.2.3)。</p>
<p>在<code>return</code>执行之前，情况是：</p>
<p><img src="img/ch05_03.png" alt="specific-move-result"></p>
<p>当我们从<code>f()</code>返回后，<code>z</code>就在其元素被<code>return</code>移出<code>f()</code>后而销毁了。
不过<code>y</code>的析构函数则将<code>delete[]</code>其元素。</p>
<p>编译器（由C++标准规定）对消除大多数跟初始化相关的复制操作负有义务，
因此转移构造函数的调用并不如你想的那样频繁。
这种<em>拷贝消除（copy elision）</em>甚至消除了转移操作中极微小的性能损失。
另一方面，隐式消除赋值操作中的复制和转移操作，几乎是不可能的，
因此，转移赋值对性能影响巨大。</p>
<h2 id="5.3">5.3 资源管理 </h2>
<p>定义构造函数、拷贝操作、转移操作和析构函数后，
程序员能完全控制所持资源（比如容器的元素）的生命期。
此外，转移构造函数可以将对象从一个作用域移到另一个，轻而易举且代价低廉。
这样，对于无法或者不该通过复制方式取出作用域的对象，就能轻易低廉地转移出来。</p>
<p><a class="en-page-number" id="73"></a></p>
<p>考量标准库的<code>thread</code>，它相当于一个并发行为(§15.2)，
以及承载上百万个<code>double</code>的<code>Vector</code>。
前者无法复制，后者不该复制。</p>
<pre><code class="lang-cpp">std::vector&lt;thread&gt; my_threads;

<span class="hljs-function">Vector <span class="hljs-title">init</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span>
</span>{
    thread t {heartbeat};               <span class="hljs-comment">// 并发运行心跳（在别的线程里）</span>
    my_threads.<span class="hljs-built_in">push_back</span>(std::<span class="hljs-built_in">move</span>(t)); <span class="hljs-comment">// 把 t 转移到 my_threads(§13.2.2)</span>
    <span class="hljs-comment">// ... 其它初始化操作 ...</span>

    <span class="hljs-function">Vector <span class="hljs-title">vec</span><span class="hljs-params">(n)</span></span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=vec.<span class="hljs-built_in">size</span>(); ++i)
        vec[i] = <span class="hljs-number">777</span>;
    <span class="hljs-keyword">return</span> vec;                         <span class="hljs-comment">// 把 vec 转移出 init()</span>
}

<span class="hljs-keyword">auto</span> v = <span class="hljs-built_in">init</span>(<span class="hljs-number">1&apos;000&apos;000</span>);               <span class="hljs-comment">// 开始心跳并初始化 v</span>
</code></pre>
<p>类似于<code>Vector</code>和<code>thread</code>这种资源执柄，在任何情况下，都是内置指针的优秀替代品。
事实上，诸如<code>unique_ptr</code>这种标准库的“智能指针（smart pointer）”，
本身就是资源执柄(§13.2.1)。</p>
<p>我用了标准库的<code>vector</code>承载<code>thread</code>，因为在 §6.2 之前，
我们简单的<code>Vector</code>还不具备元素类型参数化的能力。</p>
<p>就像让<code>new</code>和<code>delete</code>在应用代码中消失那样，我们可以让指针匿踪在资源执柄身后。
这两种情况的结果都是更简洁、更易维护的代码，而且不增加额外负担。
确切地说，可以达成<em>强资源安全（strong resource safety）</em>；
就是说，对于常规意义上的资源来说，可以消灭资源泄漏的情况。
这种例子有：<code>vector</code>持有内存、<code>thread</code>持有系统线程，以及<code>fstream</code>持有文件执柄。</p>
<p>在很多语言中，资源管理主要是委派给某个资源回收器。
C++也有个垃圾回收接口，以便你接入一个资源回收器。
但是我认为垃圾回收器是个无奈之选，
在更整洁、更通用也更接地气的资源管理器替代方案无能为力之后，才会用它。
我的观点是不要制造垃圾，这样就化解了对垃圾回收器的需求：禁止乱丢垃圾！</p>
<p>垃圾回收大体是个全局内存管理机制。
精巧的实现可以值回性能开销，但计算机系统越来越趋向于分布式
（想想缓存、多核心，以及集群），局部性比以往更重要了。</p>
<p>还有，内存并非仅有的资源。
资源是任何必须使用前（显式或隐式）申请，使用后释放的东西。
例子有内存、锁、socket、文件执柄以及线程执柄。
如你所料，内存以外的资源被称为<em>非内存资源（non-memory resource）</em>。
优秀的资源管理系统能处理所有类型的资源。
在任何长时间运行的系统里，泄漏必须避免，但资源的过度占用跟泄漏一样糟糕。
例如：如果一个系统把内存、锁、文件等，都持有双倍时长，那它就需要双倍的资源供给。</p>
<p>在采用垃圾回收器之前，请先系统化地使用资源执柄：
让每个资源都有个位于某个作用域内的有所有者，并且所有者在作用域结束处释放该资源。</p>
<p><a class="en-page-number" id="74"></a></p>
<p>在C++里，这叫
<em>RAII（资源请求即初始化 Resource Acquisition Is Initialization）</em>，
它已经跟错误处理机制中的异常整合在一起。
资源可以通过转移的语意或者“智能指针”，从一个作用域移到另一个作用域，
还可以通过“共享指针（shared pointer）”表示共享的所有权。</p>
<p>在C++标准库里，RAII无处不在：
例如内存（<code>string</code>、<code>vector</code>、<code>map</code>、<code>unordered_map</code>等），
文件（<code>ifstream</code>、<code>ofstream</code>等），线程（<code>thread</code>）,
锁（<code>lock_guard</code>,<code>unique_lock</code>等），
以及常规对象（通过<code>unique_ptr</code>和<code>shared_ptr</code>）。
其效果是隐式的资源管理，它在寻常使用中不可见，且降低了资源持有时长。</p>
<h2 id="5.4">5.4 常规操作 </h2>
<p>部分运算定义在特定类型中，具有约定俗成的意义。
程序员和库（特别是标准库）对这些约定俗成的意义会有想当然的观点，
因此在设计新类型时，如果想让运算合理，最好遵从这些意义。</p>
<ul>
<li>比较运算：<code>==</code>、<code>!=</code>、<code>&lt;</code>、<code>&lt;=</code>、<code>&gt;</code>和<code>&gt;=</code>(§5.4.1)</li>
<li>容器操作：<code>size()</code>、<code>begin()</code>和<code>end()</code>(§5.4.2)</li>
<li>输入输出操作：<code>&gt;&gt;</code>和<code>&lt;&lt;</code>(§5.4.3)</li>
<li>用户定义文本(§5.4.4)</li>
<li><code>swap()</code>(§5.4.5)</li>
<li>哈希函数：<code>hash&lt;&gt;</code>(§5.4.6)</li>
</ul>
<h3 id="5.4.1">5.4.1 比较运算 </h3>
<p>相等性比较（<code>==</code>、<code>!=</code>）的意义跟复制密切相关。在复制后，副本间应该比较相等性：</p>
<pre><code class="lang-cpp">X a = something;
X b = a;
<span class="hljs-built_in">assert</span>(a==b);   <span class="hljs-comment">// 如果在这里 a != b，就会非常怪异 (§3.5.4)</span>
</code></pre>
<p>如果定义了<code>==</code>，也要定义<code>!=</code>，并确保<code>a!=b</code>和<code>!(a==b)</code>同义。</p>
<p>类似地，如果你定义了<code>&lt;</code>，也要定义<code>&lt;=</code>、<code>&gt;</code>、<code>&gt;=</code>，还要确保符合常规相等性：</p>
<ul>
<li><code>a&lt;=b</code>、<code>(a&lt;b)||(a==b)</code>、<code>!(b&lt;a)</code>同义</li>
<li><code>a&gt;b</code>、<code>b&lt;a</code>同义</li>
<li><code>a&gt;=b</code>、<code>(a&gt;b)||(a==b)</code>、<code>!(a&lt;b)</code>同义</li>
</ul>
<p>想要对二元操作符——比如<code>==</code>——的两个操作数一视同仁，
最好在类所在的命名空间里定义一个非成员函数。
例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">namespace</span> NX { 
    <span class="hljs-keyword">class</span> <span class="hljs-title class_">X</span> {
        <span class="hljs-comment">// ...</span>
    };
    <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>==(<span class="hljs-type">const</span> X&amp;, <span class="hljs-type">const</span> X&amp;);
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p><a class="en-page-number" id="75"></a></p>
<h3 id="5.4.2">5.4.2 容器操作 </h3>
<p>除非违反的理由充分，否则设计容器应遵循标准库容器（第11章）的风格。
具体来说，需要达成容器资源安全，将其作为一个资源执柄来实现，
并附带适当的基础操作(§5.1.1, §5.2)。</p>
<p>标准库容器全都知晓其元素数量，可调用<code>size()</code>进行获取。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i&lt;c.<span class="hljs-built_in">size</span>(); ++i) <span class="hljs-comment">// size_t 是标准库 size() 返回类型的名称</span>
    c[i] = <span class="hljs-number">0</span>;
</code></pre>
<p>不过，除了用从<code>0</code>到<code>size()</code>的下标遍历容器外，
标准算法依赖于由一对<em>迭代器（iterator）</em>界定的<em>序列（sequence）</em>的概念：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p = c.<span class="hljs-built_in">begin</span>(); p!=c.<span class="hljs-built_in">end</span>(); ++p)
    *p = <span class="hljs-number">0</span>;
</code></pre>
<p>此处，<code>c.begin()</code>是个指向<code>c</code>第一个元素的迭代器，
而<code>c.end()</code>指向<code>c</code>最后一个元素之后的位置。
跟指针一样，迭代器支持<code>++</code>操作移至下一个元素，还支持<code>*</code>以访问其指向的元素的值。
<em>迭代器模型（iterator model）</em>带来了极佳的通用型和性能(§12.3)。
迭代器还被用于把序列传递给标准库算法。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-built_in">sort</span>(v.<span class="hljs-built_in">begin</span>(),v.<span class="hljs-built_in">end</span>());
</code></pre>
<p>详情及更多容器操作，请参考第11章和第12章。</p>
<p>另一个隐式使用元素数量的方式是 区间-<code>for</code>循环：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>&amp; x : c)
    x = <span class="hljs-number">0</span>;
</code></pre>
<p>这里隐式利用了<code>c.begin()</code>和<code>c.end()</code>，大体上等同于显式使用它们的循环。</p>
<h3 id="5.4.3">5.4.3 输入输出操作 </h3>
<p>对于一对整数，<code>&lt;&lt;</code>的意思是左移，<code>&gt;&gt;</code>的意思是右移。
但是，对于<code>iostream</code>，它们分别是输出和输入运算符（§1.8；第10章）。
详情及更多 I/O 操作，参见第10章。</p>
<h3 id="5.4.4">5.4.4 用户定义文本值 </h3>
<p>类的一个目标是让程序员设计、实现类型，并尽可能模拟内置类型。
构造函数提供了初始化操作，在灵活性和效率方面已经等同或超越了内置那些的初始化，
但对于内置类型来说，可以有文本值：</p>
<ul>
<li><code>123</code>是一个<code>int</code></li>
<li><code>0xFF00u</code>是一个<code>unsigned int</code></li>
<li><code>123.456</code>是一个<code>double</code></li>
<li><code>&quot;Surprise!&quot;</code>是一个<code>const char[10]</code></li>
</ul>
<p>如果用户定义类型也具备这样的文本值可就太有用了。
实现它的方法是为文本值定义适当的后缀，从而得到：</p>
<p><a class="en-page-number" id="76"></a></p>
<ul>
<li><code>&quot;Surprise!&quot;s</code>是一个<code>std::string</code></li>
<li><code>123s</code>是<strong>second</strong>（秒）</li>
<li><code>12.7i</code>是<strong>imaginary</strong>（虚部），
  因此<code>12.7i+47</code>是一个<strong>complex number</strong>（复数）（即：<code>{47, 12.7}</code>）</li>
</ul>
<p>具体来说，这些来自标准库的例子，可以借由适当的头文件和命名空间得到：</p>
<table>
<thead>
<tr>
<th></th>
<th>标准库文本值后缀</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&lt;chrono&gt;</code></td>
<td><code>std::literals::chrono_literals</code></td>
<td><code>h</code>,<code>min</code>,<code>s</code>,<code>ms</code>,<code>us</code>,<code>ns</code></td>
</tr>
<tr>
<td><code>&lt;string&gt;</code></td>
<td><code>std::literals::string_literals</code></td>
<td><code>s</code></td>
</tr>
<tr>
<td><code>&lt;string_view&gt;</code></td>
<td><code>std::literals::string_literals</code></td>
<td><code>sv</code></td>
</tr>
<tr>
<td><code>&lt;complex&gt;</code></td>
<td><code>std::literals::complex_literals</code></td>
<td><code>i</code>,<code>il</code>,<code>if</code></td>
</tr>
</tbody>
</table>
<p>不难想见，带有用户定义后缀的文本值被称为
<em>用户定义文本值（user-difined literal）</em>或<em>UDL</em>。
这些文本值通过<em>文本值操作符（literal operator）</em>定义。
文本值操作符用于转换文本值，从其带有后缀的参数类型，转化到返回值类型。
例如，<code>imaginary</code>后缀的<code>i</code>可能是这样实现的：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">constexpr</span> complex&lt;<span class="hljs-type">double</span>&gt; <span class="hljs-keyword">operator</span><span class="hljs-string">&quot;&quot;</span><span class="hljs-built_in">i</span>(<span class="hljs-type">long</span> <span class="hljs-type">double</span> arg)  <span class="hljs-comment">// 虚部文本值</span>
{
    <span class="hljs-keyword">return</span> {<span class="hljs-number">0</span>,arg};
}
</code></pre>
<p>此处：</p>
<ul>
<li><code>operator&quot;&quot;</code> 表示我们要定义一个文本值操作符</li>
<li>“文本值提示符”<code>&quot;&quot;</code>后的<code>i</code>是后缀，它从这个操作符获得意义</li>
<li>参数类型<code>long double</code>，表示后缀（<code>i</code>）是为浮点型文本值定义的</li>
<li>返回值类型<code>complex&lt;double&gt;</code>指明了结果文本值的类型</li>
</ul>
<p>据此，可以这样写：</p>
<pre><code class="lang-cpp">complex&lt;<span class="hljs-type">double</span>&gt; z = <span class="hljs-number">2.7182818</span>+<span class="hljs-number">6.283185</span>i;
</code></pre>
<h3 id="5.4.5">5.4.5 swap() </h3>
<p>有很多算法，尤其是<code>sort()</code>，会用一个<code>swap()</code>函数，交换两个对象的值。
这些算法通常假定<code>swap()</code>快速，并且不会抛出异常。
标准库提供了一个<code>std::swap(a,b)</code>，
它的实现用了三次转移操作：<code>(tmp=a, a=b, b=tmp)</code>。
假设你设计一个类型，如果复制它代价高昂又很可能被交换（比方说，被sort函数），
那么就给它定义一个转移操作，或者一个<code>swap()</code>，又或者干脆一起定义。
稍微提一下，标准库容器（第11章）和<code>string</code>(§9.2.1)具有快速转移操作。</p>
<h3 id="5.4.6">5.4.6 <code>hash&lt;&gt;</code> </h3>
<p>标准库的<code>unordered_map&lt;K,V&gt;</code>是个哈希表，其中<code>K</code>是键类型，<code>V</code>是值类型(§11.5)。
如果想用某个类型<code>X</code>作为键，就必须定义<code>hash&lt;X&gt;</code>。
标准库为我们给常见类型定义了它，比如<code>std::string</code>。</p>
<p><a class="en-page-number" id="77"></a></p>
<h2 id="5.5">5.5 忠告 </h2>
<ul>
<li>[1] 去掌控对象的构造、复制、转移以及析构；§5.1.1; [CG: R.1]。</li>
<li>[2] 把构造函数、赋值以及析构函数作为一套相互配合的操作进行设计；§5.1.1; [CG: C.22]。</li>
<li>[3] 要么定义所有基本操作，要么全都别定义；§5.1.1; [CG: C.21]。</li>
<li>[4] 如果缺省的构造函数、赋值、析构函数得当，让编译器去生成（别造轮子）§5.1.1; [CG: C.20]。</li>
<li>[5] 如果类具有指针成员，考虑一下是否需要定义一套析构函数、复制和转移的操作，或者拒绝生成它们；§5.1.1; [CG: C.32] [CG: C.33]。</li>
<li>[6] 如果类具有用户定义的析构函数，那它很可能需要用户定义的复制和转移操作，或拒绝生成它们；§5.2.1。</li>
<li>[7] 默认情况下，请把单参数构造函数声明为<code>explicit</code>；§5.1.1; [CG: C.46]。</li>
<li>[8] 如果类具有合理的默认值，就以成员变量初值的方式给定；§5.1.3; [CG: C.48]。</li>
<li>[9] 如果默认的复制操作对某个类型不得当，就重定义或者禁止它；§5.2.1, §4.6.5; [CG: C.61]。</li>
<li>[10] 将容器以传值的方式返回（相信转移操作的效率）；§5.2.2; [CG: F.20]。</li>
<li>[11] 请为大容量的操作数采用<code>const</code>引用参数类型；§5.2.2; [CG: F.16]。</li>
<li>[12] 提供强类型安全；就是说，不要泄漏任何可视为资源的东西；§5.3; [CG: R.1]。</li>
<li>[13] 如果某个类是资源执柄，那它就需要用户定义的构造函数、析构函数，以及非默认的复制操作；§5.3; [CG: R.1]。</li>
<li>[14] 请重载运算符，以便模仿约定俗成的用法；§5.4; [CG: C.160]。</li>
<li>[15] 请遵循标准库容器的设计；§5.4.2; [CG: C.100]。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 出自文章《Epigrams on Programming》：<a href="https://en.wikipedia.org/wiki/Epigrams_on_Programming" target="_blank">https://en.wikipedia.org/wiki/Epigrams_on_Programming</a><a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch04.html" class="navigation navigation-prev " aria-label="Previous page: 4 类">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch06.html" class="navigation navigation-next " aria-label="Next page: 6 模板">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":5,"title":"5 基本操作","level":"1.7","depth":1,"next":{"title":"6 模板","level":"1.8","depth":1,"path":"ch06.md","ref":"ch06.md","articles":[]},"previous":{"title":"4 类","level":"1.6","depth":1,"path":"ch04.md","ref":"ch04.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch05.md","mtime":"2023-01-13T11:28:28.128Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

